package vip.liux.front.infrastructure.annotations;

import org.aopalliance.aop.Advice;
import org.jetbrains.annotations.NotNull;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.function.SingletonSupplier;
import vip.liux.front.infrastructure.aop.interceptor.IAuditingStore;
import vip.liux.front.infrastructure.utils.AmbientScopeProvider;

import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Supplier;

public class AuditedAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {

    private final Advice advice;

    private Pointcut pointcut;

    public AuditedAnnotationAdvisor() {
        this((Supplier<IAuditingStore>) null, (Supplier<AmbientScopeProvider<?>>) null);
    }

    public AuditedAnnotationAdvisor(
            @Nullable IAuditingStore auditingStore, @Nullable AmbientScopeProvider<?> ambientScopeProvider) {
        this(SingletonSupplier.ofNullable(auditingStore), SingletonSupplier.ofNullable(ambientScopeProvider));
    }

    public AuditedAnnotationAdvisor(
            @Nullable Supplier<IAuditingStore> auditingStore, @Nullable Supplier<AmbientScopeProvider<?>> ambientScopeProvider) {

        Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<>(2);
        asyncAnnotationTypes.add(Audited.class);

        ClassLoader classLoader = AuditedAnnotationAdvisor.class.getClassLoader();

        // todo 添加其他注解的兼容?
        this.advice = buildAdvice(auditingStore, ambientScopeProvider);
        this.pointcut = buildPointcut(asyncAnnotationTypes);
    }

    public void setAuditedAnnotationType(Class<? extends Annotation> auditedAnnotationType) {
        Assert.notNull(auditedAnnotationType, "'auditedAnnotationTyp' must not be null");
        Set<Class<? extends Annotation>> auditedAnnotationTypes = new HashSet<>();
        auditedAnnotationTypes.add(auditedAnnotationType);
        this.pointcut = buildPointcut(auditedAnnotationTypes);
    }

    @Override
    public void setBeanFactory(@NotNull BeanFactory beanFactory) {
        if (this.advice instanceof BeanFactoryAware beanFactoryAware) {
            beanFactoryAware.setBeanFactory(beanFactory);
        }
    }

    @NotNull
    @Override
    public Advice getAdvice() {
        return this.advice;
    }

    @NotNull
    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }

    /**
     * 生成要为匹配方法配置的增强。
     * @param auditingStore 执行器配置
     * @param ambientScopeProvider 异常处理器配置
     * @return the configured 增强模式
     */
    protected Advice buildAdvice(
            @Nullable Supplier<IAuditingStore> auditingStore, @Nullable Supplier<AmbientScopeProvider<?>> ambientScopeProvider) {
        AnnotationAuditingInterceptor interceptor = new AnnotationAuditingInterceptor(null);
        interceptor.configure(auditingStore, ambientScopeProvider);
        return interceptor;
    }

    /**
     * 为指定的审计注释类型构建 {@link Pointcut}。
     * @param auditedAnnotationTypes the audited annotation types to introspect
     * @return the applicable {@code Pointcut}
     */
    protected Pointcut buildPointcut(Set<Class<? extends Annotation>> auditedAnnotationTypes) {
        // 构建和组合切点（Pointcut）。它提供了一种便利的方式来组合类过滤器（ClassFilter）和方法匹配器（MethodMatcher）
        ComposablePointcut result = null;
        for (Class<? extends Annotation> auditedAnnotationType : auditedAnnotationTypes) {
            // 构建一个类级别的注解匹配的切点
            Pointcut cpc = new AnnotationMatchingPointcut(auditedAnnotationType, true);
            // 构建一个方法级别的注解匹配的切点
            Pointcut mpc = new AnnotationMatchingPointcut(null, auditedAnnotationType, true);
            if (result == null) {
                result = new ComposablePointcut(cpc);
            }
            else {
                result.union(cpc);
            }
            result = result.union(mpc);
        }
        return (result != null ? result : Pointcut.TRUE);
    }
}
